#include <iostream>
#include <vector>
#include <stdio.h>
#include "channel.h"
#include "control.h"
#include <dirent.h>

#define CMD_CONV "convert"
#define CMD_CLASS "class"
#define CMD_HIST "hist"

/*
  This function is used to draw the histogram
  of the image image_name.
  Name of histogram's image: histogram.png
  Use Y-cut for construct data of histogram
 */
void drawHistogram(std::string image_name)
{
    cv::Mat image = cv::imread(image_name);
    Channel channel(image, 1);
    image = channel.preprocess(image, 0);
    cv::Mat hist_background(image.size(), CV_8UC1, cv::Scalar(255));
    channel = Channel(image, 1);
    channel.drawHistogram(hist_background, 0);
    cv::imwrite("histogram.png", hist_background);
}

/*
  After construit the histogram, this function is used to detect blocks
  blocks: list of block
  space: space between blocks
  space = "line", we detect blocks by lines
  space = "space", we detect blocks by the space
 */
void detectBlock(cv::Mat image, std::vector<Block> &blocks, std::string space = "")
{
    Channel channel(image, 1);
    bool _space;
    if(space.compare("space") == 0)
	_space = true;
    if(space.compare("line") == 0)
	_space = false;
    channel.detectBlock(blocks, _space);
}

/*
  This function is used to detect blocks without Y-cut
  Each image, we detect 100 blocks the same tail
 */
void detectBlock(cv::Mat image, std::vector<cv::Mat> &blocks)
{
    int nb_block = 10;
    if(image.channels() > 1)
	cv::cvtColor(image, image, CV_RGB2GRAY);
    Channel channel(image, 1);
    channel.detectBlock(blocks, nb_block);
}

/*
  Pour attendre l'utilisateur tapper des commandes
  Cette methode va traitter des commandes entrées pour
  réaliser des besoin en rappant des autres méthodes correspondantes
*/
void Control::processCommand(int argc, char* argv[])
{
    std::string image_path;
    std::string type1_path;
    std::string type2_path;
    std::string othertype_path;
    std::string command;
    std::string src_path, dst_path;
    std::string train_type1, train_type2;
    std::string space = "";
    double seuil = 0.5;
    /*
      OPTION
      0: pour preprocetion pour Y-cut,
      1: pour precetion pour comparaison des blocks
     */
    int option = 0;
    if(argc < 3)
	return;
    command = std::string(argv[1]);
    if(command.compare(CMD_HIST) == 0)
    {
	image_path = std::string(argv[2]);
	drawHistogram(image_path);
    }
    if(command.compare(CMD_CONV) == 0)
    {
	src_path = std::string(argv[2]);
	dst_path = std::string(argv[3]);
	option = atoi(argv[4]);
	this->convertImage(src_path, dst_path, option);
    }
    if(command.compare(CMD_CLASS) == 0)
    {
	if(argc < 10)
	    return;
	image_path = std::string(argv[2]);
	type1_path = std::string(argv[3]);
	type2_path = std::string(argv[4]);
	othertype_path = std::string(argv[5]);
	train_type1 = std::string(argv[6]);
	train_type2 = std::string(argv[7]);
	space = std::string(argv[8]);
	if(argc >= 11)
	{
	    sscanf(argv[9], "%lf", &seuil);
	    sscanf(argv[10], "%d", &option);
	}
	else sscanf(argv[9], "%d", &option);
	this->categorisation(image_path, type1_path, type2_path, othertype_path,
			     train_type1, train_type2, space, seuil, option);
    }
}

/*
  This function is used to control the categorization
  image_path: the path of image that we want make categorize
  type1_path: the destination of type 1
  type2_path: the destination of type 2
  othertype_path: the destination of type 3
  train_type1: the path of learning's data type 1
  train_type2: the path of learning's data type 2
  OPTION
  0: pour preprocetion pour Y-cut,
  1: pour precetion pour comparaison des blocks
*/
void Control::categorisation(std::string image_path, std::string type1_path,
			     std::string type2_path, std::string othertype_path,
			     std::string train_type1, std::string train_type2,
			     std::string space, double seuil, int option)
{
    std::vector<Image> images;
    Control::readImage(images, image_path);
    int N = images.size();
    int type = 0;
    std::string image_name;
    cv::Mat image_data;
    cv::Mat image_data_resize;
    std::vector<Image> type1_list, type2_list;
    std::vector<cv::Mat> type1_datas, type2_datas;
    readImage(type1_list, train_type1);
    readImage(type2_list, train_type2);
    Channel channel;
    for (int i = 0; i < (int)type1_list.size(); ++i)
    {
	cv::Mat data = channel.preprocess(type1_list[i].image_data, option);
	type1_datas.push_back(data);
    }
    for (int i = 0; i < (int)type2_list.size(); ++i)
    {
	cv::Mat data = channel.preprocess(type2_list[i].image_data, option);
	type2_datas.push_back(data);
    }
    for (int i = 0; i < N; ++i)
    {
	image_name = images[i].image_name;
	image_data = images[i].image_data;
	cv::Mat image_temp = channel.preprocess(image_data, option);
        type = this->categorisation(image_temp, type1_datas,
				    type2_datas, space, seuil);
	switch(type)
	{
	    case 1:
		image_name = type1_path + "/" + image_name;
		cv::imwrite(image_name, image_data);
		break;
	    case 2:
		image_name = type2_path + "/" + image_name;
		cv::imwrite(image_name, image_data);
		break;
	    case 3:
		image_name = othertype_path + "/" + image_name;
		cv::imwrite(image_name, image_data);
		break;
	}
    }
}

/*
  This function will return TRUE if the image in type train_type,
  if not, it return FALSE
 */
bool isCategori(cv::Mat image, cv::Mat train_type, std::string space)
{
    double error = 50;
    std::vector<Block> blocks;
    std::vector<Block> train_blocks;
    detectBlock(image, blocks, space);
    detectBlock(train_type, train_blocks, space);
    return Channel::compare(image, train_type,
			    blocks, train_blocks, error);
}

/*
  This function returns the distance between 2 image
  D = sum of (number of pixels different between 2 blocks correspondent)^2
  D = sum(N^2)
 */
double getDistance(cv::Mat image1, cv::Mat image2, bool isKnnEuclid = false)
{
    std::vector<cv::Mat> blocks;
    std::vector<cv::Mat> train_blocks;
    detectBlock(image1, blocks);
    detectBlock(image2, train_blocks);
    double distance = 0.0;
    int N = blocks.size();
    for (int i = 0; i < N; ++i)
    {
	double _distance;
	if(!isKnnEuclid)
	    _distance = Channel::different(blocks[i], train_blocks[i]);
	else
	    _distance = Channel::diffBlock(blocks[i], train_blocks[i]);
	distance += _distance*_distance;
    }
    return distance;
}

/*
  type1_list: datas trainning of type 1
  type2_list: datas trainning of type 2
  image: test image
  This function return the type code of the type correct
  1:type1, 2:type2 and 3 is type code of other
*/
int Control::categorisation(cv::Mat image, std::vector<cv::Mat> type1_list,
			    std::vector<cv::Mat> type2_list,
			    std::string space, double seuil)
{
    int type = 1;
    int nb_type1_correct = 0;
    int nb_type2_correct = 0;
    int N1 = type1_list.size();
    int N2 = type2_list.size();
    if(space.compare("auto") == 0)
    {
	std::vector<double> distances1;
	std::vector<double> distances2;
	for (int i = 0; i < N1; ++i)
	{
	    double distance = getDistance(image, type1_list[i]);
	    distances1.push_back(distance);
	}
	for (int i = 0; i < N2; ++i)
	{
	    double distance = getDistance(image, type2_list[i]);
	    distances2.push_back(distance);
	}
	double min1 = *std::min_element(distances1.begin(), distances1.end());
	double min2 = *std::min_element(distances2.begin(), distances2.end());
	std::cout<<min1 <<" " <<min2 <<std::endl;
	if(min1 < seuil*min2)
	{
	    std::cout<<min1 <<" " <<min2 <<std::endl;
	    type = 1;
	}
	else if(min2 < seuil*min1)
	{
	    std::cout<<min1 <<" " <<min2 <<std::endl;
	    type = 2;
	}
	else type = 3;
	/*
	  CODE CODE
	*/	
    }
    else if(space.compare("euclide") == 0)
    {
	std::vector<double> distances1;
	std::vector<double> distances2;
	for (int i = 0; i < N1; ++i)
	{
	    double distance = getDistance(image, type1_list[i], true);
	    distances1.push_back(distance);
	}
	for (int i = 0; i < N2; ++i)
	{
	    double distance = getDistance(image, type2_list[i], true);
	    distances2.push_back(distance);
	}
	double min1 = *std::min_element(distances1.begin(), distances1.end());
	double min2 = *std::min_element(distances2.begin(), distances2.end());
	std::cout<<min1 <<" " <<min2 <<std::endl;
	if(min1 < seuil*min2)
	{
	    std::cout<<min1 <<" " <<min2 <<std::endl;
	    type = 1;
	}
	else if(min2 < seuil*min1)
	{
	    std::cout<<min1 <<" " <<min2 <<std::endl;
	    type = 2;
	}
	else type = 3;
	/*
	  CODE CODE
	*/	
    }
    else{

	for (int i = 0; i < N1; ++i)
	{
	    if (isCategori(image, type1_list[i], space))
		nb_type1_correct++;
	}
	for (int i = 0; i < N2; ++i)
	{
	    if (isCategori(image, type2_list[i], space))
		nb_type2_correct++;
	}
	if(nb_type1_correct == 0 && nb_type2_correct == 0)
	{
	    type = 3;
	}
	else{
	    if(nb_type1_correct/(double)N1 > nb_type2_correct/(double)N2)
		type = 1;
	    else if(nb_type1_correct/(double)N1 < nb_type2_correct/(double)N2)
		type = 2;
	    else{
		//CODE
	    }
	}
    }
    return type;
}

void Control::readImage(std::vector<Image> &images, std::string path)
{
    DIR *pDIR;
    struct dirent *entry;
    std::string img_path;
    cv::Mat image_temp;
    Image image;
    pDIR=opendir(path.c_str());
    if(pDIR){
	while((entry = readdir(pDIR))){ //quand pas encore lire tous les fichiers
	    //. et .. est le répertoire actuel et le père
	    if( strcmp(entry->d_name, ".") != 0 && 
		strcmp(entry->d_name, "..") != 0 )
	    {
		img_path = path + "/" + entry->d_name;
		image_temp = cv::imread(img_path);
		if(!image_temp.data)
		    continue;
		image.image_data = image_temp;
		image.image_name = entry->d_name;
		images.push_back(image);
	    }
	}
	closedir(pDIR);
    }
}

void Control::convertImage(std::string src_path, std::string dst_path, int option)
{
    DIR *pDIR;
    struct dirent *entry;
    std::string img_path;
    cv::Mat image_temp;
    int M, N;
    cv::Mat kernel(3, 3, CV_8UC1, cv::Scalar(0));
    pDIR=opendir(src_path.c_str());
    if(pDIR){
	while((entry = readdir(pDIR))){ //quand pas encore lire tous les fichiers
	    //. et .. est le répertoire actuel et le père
	    if( strcmp(entry->d_name, ".") != 0 && 
		strcmp(entry->d_name, "..") != 0 )
	    {
		img_path = src_path + "/" + entry->d_name;
		image_temp = cv::imread(img_path);
		M = image_temp.rows;
		N = image_temp.cols;
		if(!image_temp.data)
		    continue;
		img_path = dst_path + "/" + entry->d_name;
	        cv::cvtColor(image_temp, image_temp, CV_RGB2GRAY);
		cv::threshold(image_temp, image_temp, 250, 255,
			      CV_THRESH_BINARY | CV_THRESH_OTSU);
		M = image_temp.rows;
		N = image_temp.cols;
		for (int i = 0; i < M; ++i)
		{
		    for (int j = 0; j < N; ++j)
		    {
			if(Channel::nearBoard(i, j, image_temp))
			{
			    image_temp.at<uchar>(i, j) = 255;
			}
		    }
		}
		if(option == 1)
		{
		    cv::resize(image_temp, image_temp,
			       cv::Size(image_temp.cols/5, image_temp.rows/5));
		    cv::blur(image_temp, image_temp, cv::Size(3, 3));
		}
		cv::threshold(image_temp, image_temp, 250, 255,
			      CV_THRESH_BINARY | CV_THRESH_OTSU);
		cv::imwrite(img_path, image_temp);
	    }
	}
	closedir(pDIR);
    }
}
